package mya_dc.slave;

import java.awt.Color;
import java.awt.SystemTray;
import java.awt.TrayIcon;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.util.List;
import static java.awt.SystemTray.getSystemTray;
import java.awt.AWTException;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.border.TitledBorder;

import mya_dc.shared_classes.SharedDefinitions;
import mya_dc.shared_classes.files_manipulation.FileObject;
import mya_dc.shared_classes.gui.AboutWindow;
import mya_dc.shared_classes.gui.SharedComponents;

/**
 * Implements user interface for the Slave program
 *
 * @author      Yuval Shimron
 * <br>			MYA
 */
public class SlaveGUI {
	
	// GUI shared elements 
	public JFrame	 		m_SlaveGUI;
	protected JTextField 	textListenPort;
	protected JTextField 	textServerPort;
	protected JTextField 	textHostNameIP;
	private JButton 		m_connectButton;
	private JButton			m_abortButton;
	private JButton			m_exitButton;
	private JLabel 			m_label;
	protected JCheckBox 	m_minimizeOnConnectionCheckBox;
	protected JCheckBox 	m_notifyCheckBox;
	private boolean 		bDontExit;
	private boolean 		m_bCanceled;
	private boolean			m_bMinimized;
	private TrayIcon 		m_icon; 
	
	GUIdata m_GUIdata;	
	Slave m_Parent;
	Thread m_ParentThread;
	
	// listens to communication and updates GUI as necessary
	ListnerThread lthread;

	/**
	 * Sets visibleness of UI
	 * @param b - true if visible
	 */
	public void setVisible(boolean b)
	{
		m_SlaveGUI.setVisible(b);
	}

	/**
	 * Create the application
	 */
	public SlaveGUI(Slave Parent, Thread ParentThread) {
		m_Parent = Parent;
		m_ParentThread = ParentThread;
		initialize();
		ImportData();
	}
	
	/**
	 * Imports last used user data from "GUI_slaveData.bin"
	 */
	private void ImportData()
	{
		try
		{
			String filename = System.getProperty("user.dir") + "\\gui\\GUI_slaveData.bin";
			ObjectInputStream inStream = new ObjectInputStream(new FileInputStream(filename));
			m_GUIdata = (GUIdata)inStream.readObject();
			inStream.close();
			sync();
		}
		catch(Exception e)	{}
	}
	
	/**
	 * Exports last used user data to "GUI_slaveData.bin"
	 */
	private void ExportData()
	{		
		try
		{	
			(new File(System.getProperty("user.dir") + "\\gui")).mkdir();
			String filename = System.getProperty("user.dir") + "\\gui\\GUI_slaveData.bin";
			m_GUIdata = new GUIdata(this);
			ObjectOutputStream outStream = new ObjectOutputStream(new FileOutputStream(filename));
			outStream.writeObject(m_GUIdata);		
			outStream.flush();
			outStream.close();
		}
		catch(Exception e) {}
	}
	
	/**
	 * Synchronize data from m_GUIdata
	 */
	public void sync()
	{
		Runnable doWorkRunnable = new Runnable() {
		    public void run() { 
		    	textHostNameIP.setText(m_GUIdata.sServerIP);
				textServerPort.setText(m_GUIdata.sServerPort);
				textListenPort.setText(m_GUIdata.sListenPort);
				m_minimizeOnConnectionCheckBox.setSelected(m_GUIdata.bMinimize);
				m_notifyCheckBox.setSelected(m_GUIdata.bNotify);
		    }
		};
		SwingUtilities.invokeLater(doWorkRunnable);	
	}

	/**
	 * Initialize the contents of the m_SlaveGUI
	 */
	private void initialize() {
		try 
		{
			UIManager.setLookAndFeel(
		    "com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
		}
		catch(Exception e) { }
		m_SlaveGUI = new JFrame();
		
		m_abortButton = new JButton();
		m_SlaveGUI.addWindowListener(new WindowAdapter() {			
			public void windowClosed(final WindowEvent arg0) {
				try {
					if (m_connectButton.getText().equals("Connecting..."))
					{
						// terminate the connection
						m_abortButton.doClick();
						Thread.sleep(1000); // allow 1 second to terminate
					}
					if (m_connectButton.getText().equals("Disconnect!"))
					{
						m_connectButton.doClick();
						if (bDontExit) return;
						Thread.sleep(1000); // allow 1 second to terminate
					}
					ExportData();
				}
				catch (Exception e) {}
				m_Parent.terminate();
				try { Thread.sleep(5000); } // allow 5 second to terminate
				catch (Exception e) { } 
				System.exit(0);
			}
		});
		m_SlaveGUI.getContentPane().setLayout(null);
		m_SlaveGUI.setBounds(100, 100, 326, 318);
		m_SlaveGUI.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		m_SlaveGUI.setTitle("MYA-DC Slave");

		m_label = new JLabel();
		m_label.setText("Disconnected from Server");
		m_label.setBounds(30, 225, 266, 17);
		m_label.setForeground(Color.blue);
		m_SlaveGUI.getContentPane().add(m_label);
		
		// user can cancel the connection if he clicks cancel, if the connection process is not done yet.
		m_abortButton.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				if (m_connectButton.getText().equals("Connecting..."))
				{	
					m_bCanceled = true;
					m_abortButton.setEnabled(false);
					lthread.abort = true;
					m_ParentThread.interrupt();
					try {
						// allow some time for termination of lthread:
						Thread.sleep(500);
					}
					catch (InterruptedException ie) {}
					m_Parent.terminate();
					// reset buttons:
					m_connectButton.setText("Connect!");
					m_connectButton.setEnabled(true);
					m_label.setForeground(Color.blue);
					m_label.setText("Disconnected from Server");
					setLabelsStatus(true);
				}
			}
		});
		m_abortButton.setText("Cancel");
		m_abortButton.setBounds(170, 60, 118, 27);
		m_abortButton.setEnabled(false);
		m_SlaveGUI.getContentPane().add(m_abortButton);
				
		m_connectButton = new JButton();
		m_connectButton.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent arg0) {
				// connection button has 3 states:
				// (1) "Connect!": Idle. waiting to connect.
				// (2) "Connecting...": Trying to connect
				// (3) "Disconnect!": Connection accepted. May accept and compile in background.
				if (m_connectButton.getText().equals("Connect!"))
				{
					m_connectButton.setEnabled(false);
					ExportData();
					setLabelsStatus(false);		
					m_bCanceled = false;
					m_connectButton.setText("Connecting...");
					m_label.setForeground(Color.blue);
					m_label.setText("Connecting to server...");
					m_abortButton.setEnabled(true);
				
					String strIP = textHostNameIP.getText();
					try {
						// check legal IP address or host-name
						InetAddress.getByName(strIP);
					}
					catch (Exception unknownHostException)
					{
						m_connectButton.setText("Connect!");
						setLabelsStatus(true);
						SharedComponents.MessageBox(m_SlaveGUI, SharedComponents.EMessageBoxType.ERROR_ILLEGALEHOSTNAME, 0);
						m_connectButton.setEnabled(true);
						m_abortButton.setEnabled(false);
						m_label.setForeground(Color.blue);
						m_label.setText("Disconnected from Server");
						return;
					}
					int iHostPort;
					int iListenPort;
					try {
						iHostPort = Integer.parseInt( textServerPort.getText() );
						iListenPort = Integer.parseInt( textListenPort.getText() );
					}
					catch (Exception e)
					{
						m_connectButton.setText("Connect!");
						SharedComponents.MessageBox(m_SlaveGUI, SharedComponents.EMessageBoxType.ERROR_ILLEGALEPORTS, 0);
						m_connectButton.setEnabled(true);
						m_abortButton.setEnabled(false);
						setLabelsStatus(true);
						m_label.setForeground(Color.blue);
						m_label.setText("Disconnected from Server");
						return;
					}
							
					// this thread listens and accepts connections from compilation servers.
					lthread = new ListnerThread(m_Parent,strIP, iHostPort, iListenPort);
					lthread.execute();
				}
				else if (m_connectButton.getText().equals("Disconnect!"))
				{
					// check if no compilation threads in progress.
					// if so prompt user if he's sure he wishes to abort connection.
					m_connectButton.setEnabled(false);
					if (m_Parent.runningThreads())
					{
						int terminate = JOptionPane.showConfirmDialog(m_SlaveGUI, 
								"Are you sure you wish to abort connection?\nCompilation is in progress!", 
								"Compilation in progress. Are you sure?", JOptionPane.YES_NO_OPTION);
						if (terminate == JOptionPane.NO_OPTION)
						{
							bDontExit = true;
							return;
						}
						bDontExit = false;
					}
					lthread.abort = true;
					m_ParentThread.interrupt();
					try {
						Thread.sleep(500);
					}
					catch (InterruptedException ie) {}
					
					m_Parent.terminate();
					m_label.setForeground(Color.blue);
					m_label.setText("Disconnected from Server");
					setLabelsStatus(true);
					m_connectButton.setText("Connect!");
					m_connectButton.setEnabled(true);
				}
			}	
		});
		m_connectButton.setText("Connect!");
		m_connectButton.setBounds(170, 25, 118, 27);
		m_SlaveGUI.getContentPane().add(m_connectButton);

		final JLabel mainServerIpLabel = new JLabel();
		mainServerIpLabel.setText("Main Server I.P.");
		mainServerIpLabel.setBounds(30, 20, 106, 17);
		m_SlaveGUI.getContentPane().add(mainServerIpLabel);

		final JLabel mainServerProtLabel = new JLabel();
		mainServerProtLabel.setText("Main Server Port");
		mainServerProtLabel.setBounds(30, 65, 126, 17);
		m_SlaveGUI.getContentPane().add(mainServerProtLabel);

		textHostNameIP = new JTextField();
		textHostNameIP.setToolTipText("Main server IP number");
		textHostNameIP.setBounds(30, 40, 87, 21);
		m_SlaveGUI.getContentPane().add(textHostNameIP);

		textServerPort = new JTextField();
		textServerPort.setToolTipText("Main server port number");
		textServerPort.setBounds(30, 85, 87, 21);
		m_SlaveGUI.getContentPane().add(textServerPort);
		
		final JLabel mainServerProtLabel_1 = new JLabel();
		mainServerProtLabel_1.setText("Listening Port");
		mainServerProtLabel_1.setBounds(30, 115, 126, 17);
		m_SlaveGUI.getContentPane().add(mainServerProtLabel_1);
		textListenPort = new JTextField();
		textListenPort.setToolTipText("Local listening port number");
		textListenPort.setBounds(30, 135, 87, 21);
		m_SlaveGUI.getContentPane().add(textListenPort);

		m_exitButton = new JButton();
		m_exitButton.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent arg0) {
				m_SlaveGUI.dispose();
			}
		});
		m_exitButton.setText("Exit");
		m_exitButton.setBounds(170, 95, 118, 27);
		m_SlaveGUI.getContentPane().add(m_exitButton);
		
		// maximization and size change disabling:
		m_SlaveGUI.setResizable(false); 
		// minimization support:	
		if (SystemTray.isSupported()) {
			m_SlaveGUI.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
			m_icon = new TrayIcon(SharedComponents.getImage(SharedDefinitions.Entity.ENTITY_SLAVE),
				"MYA-DC Slave - running"/*, createPopupMenu()*/);
			m_icon.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					m_bMinimized = false;
					m_SlaveGUI.setVisible(true);
					m_SlaveGUI.setExtendedState(JFrame.NORMAL);
					getSystemTray().remove(m_icon);
				}
			});
	    
			m_SlaveGUI.addWindowListener(new WindowAdapter() {
				@Override
				public void windowIconified(WindowEvent e) {
					m_bMinimized = true;
					m_SlaveGUI.setVisible(false);
					try {
						getSystemTray().add(m_icon);
					} catch (AWTException e1) {
						e1.printStackTrace();
					}
					if (m_notifyCheckBox.isSelected())
					{
						m_icon.displayMessage("Information", "MYA-DC Slave is running in background",
						TrayIcon.MessageType.INFO);
					}
				}
			});
		}

		m_minimizeOnConnectionCheckBox = new JCheckBox();
		m_minimizeOnConnectionCheckBox.setText("Minimize to tray on connection");
		m_minimizeOnConnectionCheckBox.setBounds(25, 170, 204, 25);
		m_SlaveGUI.getContentPane().add(m_minimizeOnConnectionCheckBox);
		
		m_notifyCheckBox = new JCheckBox();
		m_notifyCheckBox.setText("Show notifications when minimized");
		m_notifyCheckBox.setBounds(25, 195, 248, 25);
		m_SlaveGUI.getContentPane().add(m_notifyCheckBox);

		final JPanel panel = new JPanel();
		panel.setBorder(new TitledBorder(null, "", TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION, null, null));
		panel.setBounds(10, 10, 300, 244);
		m_SlaveGUI.getContentPane().add(panel);
		
		final JMenuBar menuBar = new JMenuBar();
		m_SlaveGUI.setJMenuBar(menuBar);
		
		final JMenu helpMenu = new JMenu();
		helpMenu.setText("Help");
		menuBar.add(helpMenu);

		final JMenuItem helpMenuItem = new JMenuItem();
		helpMenuItem.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				SharedDefinitions.openHelpFile(m_SlaveGUI);
			}
		});
		helpMenuItem.setText("Help");
		helpMenu.add(helpMenuItem);
		
		final JMenuItem aboutMenuItem = new JMenuItem();
		aboutMenuItem.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				AboutWindow.showAbout(m_SlaveGUI);
			}
		});
		aboutMenuItem.setText("About");
		helpMenu.add(aboutMenuItem);
	}
	
	/**
	 * Enables and Disables TextField
	 * 
	 * @param b - true for editable
	 */
	private void setLabelsStatus(boolean b) {
		textListenPort.setEditable(b);
		textServerPort.setEditable(b);
		textHostNameIP.setEditable(b);
	}
	
	/**
	 * Listens to Master Server for connectivity monitoring
	 *
	 * @author      Yuval Shimron
	 * <br>			MYA
	 */
	private class MainServerListener extends SwingWorker<Void, Void>
	{				
		Slave m_Parent;
		
		MainServerListener(Slave Parent)
		{
			super();
			m_Parent = Parent;
		}
		
		/**
		 * Swing worker listens in background and makes sure main server connection is alive.
		 */
		protected Void doInBackground()
		{
			m_Parent.listenToMainServer();
			return null;
		}
			
		/**
		 * When connection is lost notify GUI and maybe send notification to tray
		 */
		protected void done()
		{
			// In case of connection loss and can't reconnect  
			// terminate the program and delete all packages...
			FileObject.deleteDirectory(new File(Slave.packsDir));
			m_connectButton.setText("Connect!");
			m_connectButton.setEnabled(true);
			setLabelsStatus(true);
			m_Parent.stopListening();
			if (lthread.abort == false)
			{
				if (m_bMinimized && SystemTray.isSupported() && m_notifyCheckBox.isSelected())
				{
					m_icon.displayMessage("Warning", "Connection to Main Server was lost!",
					TrayIcon.MessageType.WARNING);
				}
				m_label.setForeground(Color.red);
				m_label.setText("Main server is unreachable. Connection lost!");
				if (!m_bMinimized)
					SharedComponents.MessageBox(m_SlaveGUI, SharedComponents.EMessageBoxType.ERROR_CONNECTIONLOST, 0);
			}
		}
	}
	
	/**
	 * Listens and accept connections from compilation servers using Slave's listenAndAccept method
	 *
	 * @author      Yuval Shimron
	 * <br>			MYA
	 */
	private class ListnerThread extends SwingWorker<Void, Integer>
	{
		public boolean abort;
		private String m_strIP;
		private int m_iServerPort;
		private int m_iListenPort;
		private Slave m_Parent;
		
		/**
		 * Constructor
		 * 
		 * @param Parent - Slave main class.
		 * @param strIP - Master server I.P.
		 * @param iServerPort - Master server port
		 * @param iListenPort - Own listening port
		 */
		ListnerThread(Slave Parent, String strIP, int iServerPort, int iListenPort)
		{
			super();
			m_strIP = strIP;
			m_iServerPort = iServerPort;
			m_iListenPort = iListenPort;
			m_Parent = Parent;
		}
		
		/**
		 * Creates networking - 
		 * <br>
		 * <li>Opens listening socket
		 * <li>Connects to master server
		 * <li>Accepts connections in loop
		 */
		protected Void doInBackground()
		{
			abort = false;
			if (!m_Parent.listen(m_iListenPort))
			{
				publish(-3);
				return null;
			}
			
			m_Parent.masterServerIP = m_strIP;
			m_Parent.masterServerPort = m_iServerPort;
			m_Parent.slaveListenPort = m_iListenPort;
			if (!m_Parent.connectToServer())
			{
				publish(-1);
				return null;
			}
			
			try {
				// wait for GUI to be updated of connectivity.
				publish(0);
				this.wait();
			}
			catch (Exception e)	{}
			
			if (abort)
				return null;
			
			int iCounter = 0;
			do {
				if (abort) return null;
				try {			
					m_Parent.listenAndAccept(iCounter);	
				}
				catch(IOException e) {
					publish(-2); // notify main thread port can't be listened at.
					m_Parent.stopListening();
					return null;
				}
				if (abort) return null;
				publish(1);
			} while (true);
		}
		
		/**
		 * Process messages from DoInBackground()
		 * <br>
		 * <br><b>Messages types</b>
		 * <br>-3: Can't open port for listening
		 * <br>-2: Error on accept connections problem.
		 * <br>-1: Connection to master server error
		 * <br>0: Successful connection
		 * <br>1: Accepted connection from a compilation server
		 */
		protected void process(List<Integer> resList)
		{
			for (int res : resList)
			switch(res) {
				case -2:
				{	
					if (m_connectButton.getText().equals("Connecting..."))
					{
						m_connectButton.setText("Connect!");
						m_connectButton.setEnabled(true);
						setLabelsStatus(true);
						m_label.setForeground(Color.red);
						m_label.setText("Can't listen to Port " + m_iListenPort + " or connection unexpected error");
						SharedComponents.MessageBox(m_SlaveGUI, SharedComponents.EMessageBoxType.ERROR_CANTLISTEN, 0);
					}
					m_Parent.stopListening();
					break;
				}
				case -1:
				{
					if (m_connectButton.getText().equals("Connecting..."))
					{
						m_connectButton.setText("Connect!");	
						m_connectButton.setEnabled(true);
						setLabelsStatus(true);
						m_label.setForeground(Color.red);
						m_label.setText("Can't connect to Main server! Check IP & port");
						SharedComponents.MessageBox(m_SlaveGUI, SharedComponents.EMessageBoxType.ERROR_CONNECTIONREFUSED, 0);
					}
					m_Parent.stopListening();
					break;
				}
				case 0:
				{
					if (!m_bCanceled)
					{
						m_connectButton.setText("Disconnect!");	
						m_connectButton.setEnabled(true);
						m_label.setForeground(Color.blue);
						m_label.setText("Connected to Server!");
						m_abortButton.setEnabled(false);
						new SlaveGUI.MainServerListener(m_Parent).execute();
						if (m_minimizeOnConnectionCheckBox.isSelected() && SystemTray.isSupported())
						{
							m_SlaveGUI.setExtendedState(JFrame.ICONIFIED); 
						}
					}
					break;
				}
				case -3:
				{
					if (m_connectButton.getText().equals("Connecting..."))
					{
						m_connectButton.setText("Connect!");
						m_label.setForeground(Color.red);
						m_label.setText("Can't listen. Please Open port " + m_iListenPort);
						SharedComponents.MessageBox(m_SlaveGUI, SharedComponents.EMessageBoxType.ERROR_CANTLISTEN, 0);
						m_connectButton.setEnabled(true);
						setLabelsStatus(true);
						break;
					}
				}
				case 1:
				{
					// a notification message
					if (m_bMinimized && SystemTray.isSupported() && m_notifyCheckBox.isSelected())
					{
						m_icon.displayMessage("Information", "Accepted connection from a Compilation Server",
						TrayIcon.MessageType.INFO);
						break;
					}
				}
			}
		}
	}
}